Slide 1: Security Aspects of Non-Financial Blockchain Applications

Overview: While blockchain enhances security, transparency, and efficiency, various attacks still threaten its applications in non-financial sectors. Security vulnerabilities, if left unaddressed, can lead to data manipulation, unauthorized access, and financial loss.


Slide 2: Security in Supply Chain Management

Potential Issues:

  1. Unauthorized Access: Unauthorized entities might attempt to manipulate product data.
  2. Reentrancy Attacks: If external functions are called within other functions, they might re-enter in a malicious way.

Solution:

  1. Role-Based Access Control (RBAC) ensures only specific roles can modify data.
  2. Use of nonReentrant modifier to prevent reentrancy attacks.

Code Sample: Updated Supply Chain Tracking Contract with Security Enhancements

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract SupplyChain is ReentrancyGuard {
    address public admin;
    mapping(address => bool) public authorizedEntities;
    mapping(address => uint) public roles; // 0: ADMIN, 1: AUTHORIZER, 2: MANUFACTURER

    struct Product {
        string name;
        address manufacturer;
        bool isVerified;
    }

    mapping(uint => Product) public products;

    constructor() {
        admin = msg.sender;
        roles[msg.sender] = 0; // Assign admin role to deployer
    }

    modifier onlyAdmin() {
        require(roles[msg.sender] == 0, "Only admin can perform this action");
        _;
    }

    modifier onlyAuthorized() {
        require(authorizedEntities[msg.sender] || roles[msg.sender] == 0, "Not authorized");
        _;
    }

    modifier onlyManufacturer(uint productId) {
        require(msg.sender == products[productId].manufacturer || roles[msg.sender] == 0, "Only manufacturer can verify");
        _;
    }

    function authorizeEntity(address entity, uint role) public onlyAdmin {
        authorizedEntities[entity] = true;
        roles[entity] = role;
    }

    function addProduct(uint productId, string memory name) public onlyAuthorized nonReentrant {
        products[productId] = Product(name, msg.sender, false);
    }

    function verifyProduct(uint productId) public onlyManufacturer(productId) nonReentrant {
        products[productId].isVerified = true;
    }
}

Prevention Insights:


Slide 3: Healthcare Data Management Security

Potential Issues:

  1. Data Privacy: Sensitive medical data should be restricted to authorized access.
  2. Data Immutability: Incorrect entries are immutable, making error correction difficult.

Solution:

  1. Strict Input Validation: Validates data before storage.
  2. Zero-Knowledge Proofs (Advanced): For selective data visibility.

Code Sample: Enhanced Medical Record Storage

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

contract SecureMedicalRecords is AccessControl, ReentrancyGuard {
    using ECDSA for bytes32;

    // Roles
    bytes32 public constant DOCTOR_ROLE = keccak256("DOCTOR_ROLE");
    bytes32 public constant PATIENT_ROLE = keccak256("PATIENT_ROLE");

    struct Record {
        bytes32 encryptedDiagnosis; // Encrypted with patient’s private key for privacy
        bytes32 encryptedTreatment;
        uint256 timestamp;
    }

    // Mapping of patient addresses to medical records
    mapping(address => Record[]) private records;

    // Event logs for monitoring
    event RecordAdded(address indexed patient, uint256 timestamp);

    constructor() {
        _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); // Deployer as admin
    }

    /**
     * @notice Adds a new medical record for the patient.
     * @dev Only the assigned doctor can add records for a patient.
     * @param patient The address of the patient.
     * @param encryptedDiagnosis Hashed diagnosis encrypted with patient’s public key.
     * @param encryptedTreatment Hashed treatment encrypted with patient’s public key.
     */
    function addRecord(
        address patient,
        bytes32 encryptedDiagnosis,
        bytes32 encryptedTreatment
    ) public nonReentrant onlyRole(DOCTOR_ROLE) {
        require(hasRole(PATIENT_ROLE, patient), "Not a registered patient");

        // Save the record securely
        records[patient].push(
            Record(encryptedDiagnosis, encryptedTreatment, block.timestamp)
        );
        emit RecordAdded(patient, block.timestamp);
    }

    /**
     * @notice Allows a patient to view their own records.
     * @dev Only the patient can access their records, not doctors.
     * @return Array of encrypted medical records.
     */
    function viewRecords() external view onlyRole(PATIENT_ROLE) returns (Record[] memory) {
        return records[msg.sender];
    }

    /**
     * @notice Grants the doctor role to an address.
     * @dev Only admins can add doctors.
     * @param doctor The address to be granted the doctor role.
     */
    function addDoctor(address doctor) public onlyRole(DEFAULT_ADMIN_ROLE) {
        grantRole(DOCTOR_ROLE, doctor);
    }

    /**
     * @notice Registers a new patient.
     * @dev Only admins can register patients.
     * @param patient The address of the patient.
     */
    function registerPatient(address patient) public onlyRole(DEFAULT_ADMIN_ROLE) {
        grantRole(PATIENT_ROLE, patient);
    }
}

Role-Based Access Control:

Prevention Insights:


Slide 4: Government Applications Security

Potential Issues:

  1. Sybil Attacks: Fake identities can skew voting results.
  2. Replay Attacks: Re-use of valid transactions by malicious actors.

Solution:

  1. Commit-Reveal Voting: Prevents premature disclosure and enforces anonymity.
  2. Nonce Mechanism: Prevents replay attacks by attaching a unique nonce to each vote.

Code Sample: Improved Voting System with Commit-Reveal Scheme

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SecureVoting {
    struct Voter {
        bytes32 commitment;
        bool revealed;
        uint8 vote;
    }

    mapping(address => Voter) private voters;
    uint256 public yesVotes;
    uint256 public noVotes;
    uint256 public commitEndTime;
    uint256 public revealEndTime;

    modifier onlyDuringCommitPhase() {
        require(block.timestamp < commitEndTime, "Commit phase has ended");
        _;
    }

    modifier onlyDuringRevealPhase() {
        require(block.timestamp >= commitEndTime && block.timestamp < revealEndTime, "Reveal phase not active");
        _;
    }

    function commitVote(bytes32 _commitment) external onlyDuringCommitPhase {
        require(voters[msg.sender].commitment == bytes32(0), "Already committed");
        voters[msg.sender].commitment = _commitment;
    }

    function revealVote(uint8 _vote, string calldata _salt) external onlyDuringRevealPhase {
        Voter storage voter = voters[msg.sender];
        require(!voter.revealed, "Already revealed");
        require(_vote == 1 || _vote == 0, "Vote must be 0 or 1");

        bytes32 checkCommitment = keccak256(abi.encodePacked(_vote, _salt));
        require(checkCommitment == voter.commitment, "Commitment mismatch");

        voter.revealed = true;
        if (_vote == 1) yesVotes++;
        else noVotes++;
    }

    function getResults() public view returns (string memory) {
        if (yesVotes > noVotes) return "Yes Wins";
        else if (noVotes > yesVotes) return "No Wins";
        else return "Tie";
    }
}

Prevention Insights:


Slide 5: Mitigating Flash Loan and Reentrancy Attacks in DeFi

Potential Issues:

  1. Flash Loan Attacks: Exploits temporary loans within a single transaction.
  2. Reentrancy Attacks: Recursive calls exploit contract logic to drain funds.

Solution:

  1. Reentrancy Guard prevents re-entrant calls.
  2. Oracle-Based Time-Weighted Averages stabilize prices to prevent flash loan manipulation.

Code Sample: Secure Lending Contract with Reentrancy Prevention

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract SecureLending is ReentrancyGuard {
    mapping(address => uint) public balances;

    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw(uint amount) public nonReentrant {
        require(balances[msg.sender] >= amount, "Insufficient balance");
        balances[msg.sender] -= amount;
        payable(msg.sender).transfer(amount);
    }
}

Prevention Insights:


Some Generic Attacks

1. Unauthorized Access

Problem: Unauthorized access allows attackers to view, modify, or delete data without permission, which can expose sensitive information or disrupt services.

Solution: Implement Role-Based Access Control (RBAC). Assign roles like DOCTOR_ROLE and PATIENT_ROLE to restrict access to certain functions.

Code Example:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/AccessControl.sol";

contract MedicalRecords is AccessControl {
    bytes32 public constant DOCTOR_ROLE = keccak256("DOCTOR_ROLE");
    bytes32 public constant PATIENT_ROLE = keccak256("PATIENT_ROLE");

    struct Record {
        string diagnosis;
        string treatment;
        uint256 timestamp;
    }

    mapping(address => Record[]) private records;

    constructor() {
        _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
    }

    function addDoctor(address doctor) public onlyRole(DEFAULT_ADMIN_ROLE) {
        grantRole(DOCTOR_ROLE, doctor);
    }

    function addPatient(address patient) public onlyRole(DEFAULT_ADMIN_ROLE) {
        grantRole(PATIENT_ROLE, patient);
    }

    function addRecord(address patient, string memory diagnosis, string memory treatment) public onlyRole(DOCTOR_ROLE) {
        records[patient].push(Record(diagnosis, treatment, block.timestamp));
    }

    function viewRecords() public view onlyRole(PATIENT_ROLE) returns (Record[] memory) {
        return records[msg.sender];
    }
}

In this contract, only users with the DOCTOR_ROLE can add medical records, and only those with the PATIENT_ROLE can view their own records.


2. Reentrancy Attacks

Problem: A reentrancy attack happens when a function calls another contract which then calls back into the first contract before the initial execution is completed. This can drain funds or manipulate data.

AttackerContractVictimCalls withdraw()Executes withdraw() logicSends fundsReenters withdraw() before state changeExecutes withdraw() againSends more fundsRepeat until funds are drainedAttackerContractVictim

Solution: Use the nonReentrant modifier to prevent functions from being called recursively.

Code Example:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract SecureBank is ReentrancyGuard {
    mapping(address => uint256) public balances;

    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw(uint256 amount) public nonReentrant {
        require(balances[msg.sender] >= amount, "Insufficient balance");
        balances[msg.sender] -= amount;
        payable(msg.sender).transfer(amount);
    }
}

Here, the nonReentrant modifier prevents reentrancy attacks, as it disallows re-entry into the withdraw function until the current execution is completed.


3. Strict Input Validation

Problem: Unvalidated inputs can allow for data corruption, injection attacks, and other unintended behaviors.

Solution: Implement checks for each input to ensure they meet expected parameters (e.g., non-empty strings, within numerical ranges).

Code Example:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract InputValidatedSystem {
    struct User {
        string name;
        uint8 age;
    }

    mapping(address => User) public users;

    function registerUser(string memory name, uint8 age) public {
        require(bytes(name).length > 0, "Name cannot be empty");
        require(age > 0 && age < 120, "Invalid age range");

        users[msg.sender] = User(name, age);
    }
}

This example ensures the name is not empty and that age is within a reasonable range before adding the user to the system.


4. Sybil Attacks

Problem: In a Sybil attack, the attacker creates multiple fake identities to gain control or manipulate consensus.

AttackerContractVoter1Voter2Voter3Creates fake identity (Voter1)Creates fake identity (Voter2)Creates fake identity (Voter3)Casts vote (Yes)Casts vote (Yes)Casts vote (No)Tally votes (Manipulated)Receives manipulated vote resultManipulated voting outcome by creating multiple fake identitiesAttackerContractVoter1Voter2Voter3

Solution: Use Proof of Identity by requiring verified identity data, or stake-based requirements (e.g., requiring deposits or reputation).

Code Example:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract StakeBasedVoting {
    struct Voter {
        uint256 stake;
        bool hasVoted;
        uint8 vote;
        uint256 lockTimestamp;
    }

    mapping(address => Voter) public voters;
    uint256 public yesVotes;
    uint256 public noVotes;
    uint256 public constant MINIMUM_STAKE = 1 ether; // Require 1 ETH stake to vote
    uint256 public constant WITHDRAW_DELAY = 1 days; // Lock funds for 24 hours after voting ends
    uint256 public votingEndTime;

    event VoteCasted(address indexed voter, uint8 vote);
    event StakeWithdrawn(address indexed voter, uint256 amount);

    modifier onlyDuringVoting() {
        require(block.timestamp < votingEndTime, "Voting has ended");
        _;
    }

    modifier onlyAfterVoting() {
        require(block.timestamp >= votingEndTime, "Voting is still ongoing");
        _;
    }

    constructor(uint256 _votingDuration) {
        votingEndTime = block.timestamp + _votingDuration;
    }

    function vote(uint8 _vote) external payable onlyDuringVoting {
        require(_vote == 0 || _vote == 1, "Invalid vote"); // 0 = No, 1 = Yes
        require(msg.value >= MINIMUM_STAKE, "Minimum stake required");
        require(!voters[msg.sender].hasVoted, "Already voted");

        voters[msg.sender] = Voter({
            stake: msg.value,
            hasVoted: true,
            vote: _vote,
            lockTimestamp: block.timestamp + WITHDRAW_DELAY
        });

        if (_vote == 1) {
            yesVotes++;
        } else {
            noVotes++;
        }

        emit VoteCasted(msg.sender, _vote);
    }

    function withdrawStake() external onlyAfterVoting {
        Voter storage voter = voters[msg.sender];
        require(voter.hasVoted, "No stake to withdraw");
        require(block.timestamp >= voter.lockTimestamp, "Stake locked");

        uint256 amount = voter.stake;
        voter.stake = 0;
        
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed");

        emit StakeWithdrawn(msg.sender, amount);
    }

    function getResult() external view returns (string memory) {
        require(block.timestamp >= votingEndTime, "Voting is still ongoing");

        if (yesVotes > noVotes) {
            return "Yes wins";
        } else if (noVotes > yesVotes) {
            return "No wins";
        } else {
            return "It's a tie";
        }
    }
}

This example requires voters to deposit funds proportional to their weight, making it costly for attackers to create fake identities.


5. Replay Attacks

Problem: Replay attacks involve resending a previously valid transaction to perform unintended actions.

AttackerVictimContractVictim initiates a valid transaction (e.g., transfer funds)Executes valid transaction (transfer funds)Transaction successfulReplays the same transactionExecutes replayed transaction (fund transfer)Attacker successfully replays the transaction to steal funds or duplicate actionsAttackerVictimContract

Solution: Use nonces to uniquely identify each transaction and ensure that repeated transactions are invalid.

Code Example:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SecureNonce {
    mapping(address => uint256) private nonces;

    function getSecureNonce(address user) public view returns (uint256) {
        // Generate a secure nonce using keccak256 with a mix of variables
        return uint256(keccak256(abi.encodePacked(user, nonces[user], block.timestamp)));
    }

    function executeTransaction(uint256 providedNonce, uint256 amount) public {
        uint256 expectedNonce = getSecureNonce(msg.sender);
        require(providedNonce == expectedNonce, "Invalid nonce provided");

        // Increment nonce for user to ensure each transaction is unique
        nonces[msg.sender]++;
        
        // Further transaction logic
    }
}

The nonce is a keccak256 hash based on the user’s address, their current nonce count, and the block’s timestamp, creating a more complex and unique identifier.


6. Flash Loan Attacks

Problem: Flash loan attacks exploit quick, uncollateralized loans to manipulate prices or other systems within a single transaction.

AttackerContractDEXRequests flash loan (e.g., 1000 ETH)Provides loan (no collateral)Manipulates token price on DEX (e.g., by swapping tokens)Executes token trade (price manipulation)Repays flash loan (1000 ETH + fees)Loan repayment successfulProfits from price manipulation and market arbitrageAttacker manipulates market, profits, and repays the loan in one transactionAttackerContractDEX

Solution: Use price oracles with time-weighted averages (TWAP) to prevent price manipulation within a single transaction.

Code Example:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";

contract TWAPPriceFeed {
    AggregatorV3Interface internal priceFeed;
    uint256[] private historicalPrices;
    uint256 constant INTERVAL = 10 minutes;
    uint256 constant DURATION = 1 hours;
    
    constructor(address priceFeedAddress) {
        priceFeed = AggregatorV3Interface(priceFeedAddress);
    }

    function updatePrice() public {
        require(block.timestamp % INTERVAL == 0, "Updates only every 10 minutes");
        
        (, int price,,,) = priceFeed.latestRoundData();
        historicalPrices.push(uint256(price));
        
        if (historicalPrices.length > DURATION / INTERVAL) {
            // Keep only the latest prices within the duration window
            historicalPrices.pop();
        }
    }

    function getTWAP() public view returns (uint256) {
        require(historicalPrices.length > 0, "No price data available");

        uint256 sumPrices = 0;
        for (uint256 i = 0; i < historicalPrices.length; i++) {
            sumPrices += historicalPrices[i];
        }
        
        return sumPrices / historicalPrices.length;
    }
}

AttackerContractDEXPriceFeedRequests flash loan (e.g., 1000 ETH)Provides loan (no collateral)Manipulates token price (instant manipulation with flash loan)Executes token trade (price manipulation)Get latest price (from Chainlink)Returns price (based on multiple data points, not a single price)Calculate TWAP (time-weighted average price)Rejects manipulated price (due to TWAP filtering out sudden changes)Repays flash loan (1000 ETH + fees)Loan repayment successful (attack fails)Flash loan attack fails because TWAP mitigates sudden price manipulationAttackerContractDEXPriceFeed

A TWAP oracle mitigates flash loan manipulation by taking average prices over a period, which limits the effects of momentary spikes or dips.